package chapter05;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;

import static java.lang.System.currentTimeMillis;
import static java.lang.Thread.currentThread;

/**
 * @author chisheng
 * @since 2021-11-07
 */
public class BooleanLock implements Lock {

    /**
     * 代表当前拥有锁的线程
     */
    private Thread currentThread;

    /**
     * true 表示当前锁被某个线程获得；
     * false 表示当前锁没有被任何线程获得或已经释放
     */
    private boolean locked = false;

    /**
     * 处于阻塞状态的线程队列
     */
    private final List<Thread> blockedList = new ArrayList<>();

    @Override
    public void lock() throws InterruptedException {
        // this 表示控制使用该锁的所有对象
        synchronized (this) {
            // 通过 locked 的值判断是否能获取到锁
            while (locked) {
                // 不能获取到锁则进入阻塞状态
                if (!blockedList.contains(currentThread())) {
                    blockedList.add(currentThread());
                }

                try {
                    this.wait();
                } catch (InterruptedException e) {
                    // 若等待被中断，当前线程从等待队列中移除
                    blockedList.remove(currentThread());
                    throw e;
                }
            }
        }

        // 从阻塞队列中移除该线程（尽管该线程从未加入到阻塞队列中，该移除方法仍能正常执行）
        blockedList.remove(currentThread());
        // 标识线程获取到锁
        this.locked = true;
        this.currentThread = currentThread();
    }

    @Override
    public void lock(long millis) throws InterruptedException, TimeoutException {
        // 等待小于 0 抛出非法参数异常
        if (millis < 0) {
            throw new IllegalArgumentException("The parameter millis must be greater than 0");
        }

        // this 表示控制使用该锁的所有对象
        synchronized (this) {
            // 若等待时间为 0 则使用 lock 方式实现
            if (millis == 0) {
                this.lock();
            } else {
                long remainingMills = millis;
                long endMills = currentTimeMillis() + remainingMills;
                while (locked) {
                    // 若超过等待时间仍未获得锁则抛出超时异常
                    if (remainingMills <= 0) {
                        throw new TimeoutException("Can not get the lock during " + millis);
                    }
                    // 由于锁被占用，则将当前请求锁的线程挂在阻塞队列中
                    if (!blockedList.contains(currentThread())) {
                        blockedList.add(currentThread());
                    }

                    // 让当前请求锁线程等待指定的时间后苏醒
                    this.wait(remainingMills);
                    // 重新计算需要等待的时间
                    remainingMills = endMills - currentTimeMillis();
                }

                // 请求的到锁，将当前当前线程移除等待队列，并标识获的锁
                blockedList.remove(currentThread());
                this.locked = true;
                this.currentThread = currentThread();
            }
        }
    }

    @Override
    public void unlock() {
        synchronized (this) {
            // 判断调用释放锁线程是否持有锁，若持有则释放，否则忽略本次调用
            if (currentThread == currentThread()) {
                // 标识锁被释放
                locked = false;
                Optional.of(currentThread().getName() + " release the lock.").ifPresent(System.out::println);
                // 唤醒应请求锁而被阻塞的所有线程
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        // 返回一个只读列表
        return Collections.unmodifiableList(blockedList);
    }

}
